//
//  NSObject+QTSwizzle.m
//  QTCategory
//
//  Created by 张俊博 on 2017/2/8.
//  Copyright © 2017年 CISDI. All rights reserved.
//

//#import "NSObject+QTSwizzle.h"
//#import <libkern/OSAtomic.h>
//#import <objc/runtime.h>
//#import <objc/message.h>
//
//#define QTSwizzleLog(...)
////#define QTSwizzleLog(...) do { NSLog(__VA_ARGS__); }while(0)
//#define QTSwizzleLogError(...) do { NSLog(__VA_ARGS__); }while(0)
//
//typedef NS_OPTIONS(int, QTSwizzleBlockFlags) {
//    QTSwizzleBlockFlagsHasCopyDisposeHelpers = (1 << 25),
//    QTSwizzleBlockFlagsHasSignature          = (1 << 30)
//};
//typedef struct _QTSwizzleBlock {
//    __unused Class isa;
//    QTSwizzleBlockFlags flags;
//    __unused int reserved;
//    void (__unused *invoke)(struct _QTSwizzleBlock *block, ...);
//    struct {
//        unsigned long int reserved;
//        unsigned long int size;
//        // requires QTSwizzleBlockFlagsHasCopyDisposeHelpers
//        void (*copy)(void *dst, const void *src);
//        void (*dispose)(const void *);
//        // requires QTSwizzleBlockFlagsHasSignature
//        const char *signature;
//        const char *layout;
//    } *descriptor;
//    // imported variables
//} *QTSwizzleBlockRef;
//
//@interface QTSwizzleInfo : NSObject <QTSwizzleInfo>
//- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
//@property (nonatomic, unsafe_unretained, readonly) id instance;
//@property (nonatomic, strong, readonly) NSArray *arguments;
//@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
//@end
//
//// Tracks a single QTSwizzle.
//@interface QTSwizzleIdentifier : NSObject
//+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(QTSwizzleOptions)options block:(id)block error:(NSError **)error;
//- (BOOL)invokeWithInfo:(id<QTSwizzleInfo>)info;
//@property (nonatomic, assign) SEL selector;
//@property (nonatomic, strong) id block;
//@property (nonatomic, strong) NSMethodSignature *blockSignature;
//@property (nonatomic, weak) id object;
//@property (nonatomic, assign) QTSwizzleOptions options;
//@end
//
//// Tracks all QTSwizzles for an object/class.
//@interface QTSwizzlesContainer : NSObject
//- (void)addQTSwizzle:(QTSwizzleIdentifier *)QTSwizzle withOptions:(QTSwizzleOptions)injectPosition;
//- (BOOL)removeQTSwizzle:(id)QTSwizzle;
//- (BOOL)hasQTSwizzles;
//@property (atomic, copy) NSArray *beforeQTSwizzles;
//@property (atomic, copy) NSArray *insteadQTSwizzles;
//@property (atomic, copy) NSArray *afterQTSwizzles;
//@end
//
//@interface QTSwizzleTracker : NSObject
//- (id)initWithTrackedClass:(Class)trackedClass;
//@property (nonatomic, strong) Class trackedClass;
//@property (nonatomic, readonly) NSString *trackedClassName;
//@property (nonatomic, strong) NSMutableSet *selectorNames;
//@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
//- (void)addSubclassTracker:(QTSwizzleTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
//- (void)removeSubclassTracker:(QTSwizzleTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
//- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
//- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
//@end
//
//@interface NSInvocation (QTSwizzles)
//- (NSArray *)swizzles_arguments;
//@end
//
//#define QTSwizzlePositionFilter 0x07
//
//#define QTSwizzleError(errorCode, errorDescription) do { \
//QTSwizzleLogError(@"QTSwizzles: %@", errorDescription); \
//if (error) { *error = [NSError errorWithDomain:QTSwizzleErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0)
//
//NSString *const QTSwizzleErrorDomain = @"QTSwizzleErrorDomain";
//static NSString *const QTSwizzlesSubclassSuffix = @"_QTSwizzles_";
//static NSString *const QTSwizzlesMessagePrefix = @"QTSwizzles_";
//
//@implementation NSObject (QTSwizzle)
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - Public QTSwizzles API
//
//+ (id<QTSwizzleToken>)swizzle_hookSelector:(SEL)selector
//                           withOptions:(QTSwizzleOptions)options
//                            usingBlock:(id)block
//                                 error:(NSError **)error {
//    return swizzle_add((id)self, selector, options, block, error);
//}
//
///// @return A token which allows to later deregister the QTSwizzle.
//- (id<QTSwizzleToken>)swizzle_hookSelector:(SEL)selector
//                           withOptions:(QTSwizzleOptions)options
//                            usingBlock:(id)block
//                                 error:(NSError **)error {
//    return swizzle_add(self, selector, options, block, error);
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - Private Helper
//
//static id swizzle_add(id self, SEL selector, QTSwizzleOptions options, id block, NSError **error) {
//    NSCParameterAssert(self);
//    NSCParameterAssert(selector);
//    NSCParameterAssert(block);
//    
//    __block QTSwizzleIdentifier *identifier = nil;
//    swizzle_performLocked(^{
//        if (swizzle_isSelectorAllowedAndTrack(self, selector, options, error)) {
//            QTSwizzlesContainer *QTSwizzleContainer = swizzle_getContainerForObject(self, selector);
//            identifier = [QTSwizzleIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
//            if (identifier) {
//                [QTSwizzleContainer addQTSwizzle:identifier withOptions:options];
//                
//                // Modify the class to allow message interception.
//                swizzle_prepareClassAndHookSelector(self, selector, error);
//            }
//        }
//    });
//    return identifier;
//}
//
//static BOOL swizzle_remove(QTSwizzleIdentifier *QTSwizzle, NSError **error) {
//    NSCAssert([QTSwizzle isKindOfClass:QTSwizzleIdentifier.class], @"Must have correct type.");
//    
//    __block BOOL success = NO;
//    swizzle_performLocked(^{
//        id self = QTSwizzle.object; // strongify
//        if (self) {
//            QTSwizzlesContainer *QTSwizzleContainer = swizzle_getContainerForObject(self, QTSwizzle.selector);
//            success = [QTSwizzleContainer removeQTSwizzle:QTSwizzle];
//            
//            swizzle_cleanupHookedClassAndSelector(self, QTSwizzle.selector);
//            // destroy token
//            QTSwizzle.object = nil;
//            QTSwizzle.block = nil;
//            QTSwizzle.selector = NULL;
//        }else {
//            NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", QTSwizzle];
//            QTSwizzleError(QTSwizzleErrorRemoveObjectAlreadyDeallocated, errrorDesc);
//        }
//    });
//    return success;
//}
//
//static void swizzle_performLocked(dispatch_block_t block) {
//    static OSSpinLock QTSwizzle_lock = OS_SPINLOCK_INIT;
//    OSSpinLockLock(&QTSwizzle_lock);
//    block();
//    OSSpinLockUnlock(&QTSwizzle_lock);
//}
//
//static SEL swizzle_aliasForSelector(SEL selector) {
//    NSCParameterAssert(selector);
//    return NSSelectorFromString([QTSwizzlesMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
//}
//
//static NSMethodSignature *swizzle_blockMethodSignature(id block, NSError **error) {
//    QTSwizzleBlockRef layout = (__bridge void *)block;
//    if (!(layout->flags & QTSwizzleBlockFlagsHasSignature)) {
//        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
//        QTSwizzleError(QTSwizzleErrorMissingBlockSignature, description);
//        return nil;
//    }
//    void *desc = layout->descriptor;
//    desc += 2 * sizeof(unsigned long int);
//    if (layout->flags & QTSwizzleBlockFlagsHasCopyDisposeHelpers) {
//        desc += 2 * sizeof(void *);
//    }
//    if (!desc) {
//        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
//        QTSwizzleError(QTSwizzleErrorMissingBlockSignature, description);
//        return nil;
//    }
//    const char *signature = (*(const char **)desc);
//    return [NSMethodSignature signatureWithObjCTypes:signature];
//}
//
//static BOOL swizzle_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
//    NSCParameterAssert(blockSignature);
//    NSCParameterAssert(object);
//    NSCParameterAssert(selector);
//    
//    BOOL signaturesMatch = YES;
//    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
//    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
//        signaturesMatch = NO;
//    }else {
//        if (blockSignature.numberOfArguments > 1) {
//            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
//            if (blockType[0] != '@') {
//                signaturesMatch = NO;
//            }
//        }
//        // Argument 0 is self/block, argument 1 is SEL or id<QTSwizzleInfo>. We start comparing at argument 2.
//        // The block can have less arguments than the method, that's ok.
//        if (signaturesMatch) {
//            for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
//                const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
//                const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
//                // Only compare parameter, not the optional type data.
//                if (!methodType || !blockType || methodType[0] != blockType[0]) {
//                    signaturesMatch = NO; break;
//                }
//            }
//        }
//    }
//    
//    if (!signaturesMatch) {
//        NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
//        QTSwizzleError(QTSwizzleErrorIncompatibleBlockSignature, description);
//        return NO;
//    }
//    return YES;
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - Class + Selector Preparation
//
//static BOOL swizzle_isMsgForwardIMP(IMP impl) {
//    return impl == _objc_msgForward
//#if !defined(__arm64__)
//    || impl == (IMP)_objc_msgForward_stret
//#endif
//    ;
//}
//
//static IMP swizzle_getMsgForwardIMP(NSObject *self, SEL selector) {
//    IMP msgForwardIMP = _objc_msgForward;
//#if !defined(__arm64__)
//    // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
//    // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
//    // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
//    // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
//    Method method = class_getInstanceMethod(self.class, selector);
//    const char *encoding = method_getTypeEncoding(method);
//    BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
//    if (methodReturnsStructValue) {
//        @try {
//            NSUInteger valueSize = 0;
//            NSGetSizeAndAlignment(encoding, &valueSize, NULL);
//            
//            if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
//                methodReturnsStructValue = NO;
//            }
//        } @catch (__unused NSException *e) {}
//    }
//    if (methodReturnsStructValue) {
//        msgForwardIMP = (IMP)_objc_msgForward_stret;
//    }
//#endif
//    return msgForwardIMP;
//}
//
//static void swizzle_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
//    NSCParameterAssert(selector);
//    Class klass = swizzle_hookClass(self, error);
//    Method targetMethod = class_getInstanceMethod(klass, selector);
//    IMP targetMethodIMP = method_getImplementation(targetMethod);
//    if (!swizzle_isMsgForwardIMP(targetMethodIMP)) {
//        // Make a method alias for the existing method implementation, it not already copied.
//        const char *typeEncoding = method_getTypeEncoding(targetMethod);
//        SEL aliasSelector = swizzle_aliasForSelector(selector);
//        if (![klass instancesRespondToSelector:aliasSelector]) {
//            __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
//            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
//        }
//        
//        // We use forwardInvocation to hook in.
//        class_replaceMethod(klass, selector, swizzle_getMsgForwardIMP(self, selector), typeEncoding);
//        QTSwizzleLog(@"QTSwizzles: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
//    }
//}
//
//// Will undo the runtime changes made.
//static void swizzle_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
//    NSCParameterAssert(self);
//    NSCParameterAssert(selector);
//    
//    Class klass = object_getClass(self);
//    BOOL isMetaClass = class_isMetaClass(klass);
//    if (isMetaClass) {
//        klass = (Class)self;
//    }
//    
//    // Check if the method is marked as forwarded and undo that.
//    Method targetMethod = class_getInstanceMethod(klass, selector);
//    IMP targetMethodIMP = method_getImplementation(targetMethod);
//    if (swizzle_isMsgForwardIMP(targetMethodIMP)) {
//        // Restore the original method implementation.
//        const char *typeEncoding = method_getTypeEncoding(targetMethod);
//        SEL aliasSelector = swizzle_aliasForSelector(selector);
//        Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
//        IMP originalIMP = method_getImplementation(originalMethod);
//        NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
//        
//        class_replaceMethod(klass, selector, originalIMP, typeEncoding);
//        QTSwizzleLog(@"QTSwizzles: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
//    }
//    
//    // Deregister global tracked selector
//    swizzle_deregisterTrackedSelector(self, selector);
//    
//    // Get the QTSwizzle container and check if there are any hooks remaining. Clean up if there are not.
//    QTSwizzlesContainer *container = swizzle_getContainerForObject(self, selector);
//    if (!container.hasQTSwizzles) {
//        // Destroy the container
//        swizzle_destroyContainerForObject(self, selector);
//        
//        // Figure out how the class was modified to undo the changes.
//        NSString *className = NSStringFromClass(klass);
//        if ([className hasSuffix:QTSwizzlesSubclassSuffix]) {
//            Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:QTSwizzlesSubclassSuffix withString:@""]);
//            NSCAssert(originalClass != nil, @"Original class must exist");
//            object_setClass(self, originalClass);
//            QTSwizzleLog(@"QTSwizzles: %@ has been restored.", NSStringFromClass(originalClass));
//            
//            // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
//            // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
//            //objc_disposeClassPair(object.class);
//        }else {
//            // Class is most likely swizzled in place. Undo that.
//            if (isMetaClass) {
//                swizzle_undoSwizzleClassInPlace((Class)self);
//            }else if (self.class != klass) {
//                swizzle_undoSwizzleClassInPlace(klass);
//            }
//        }
//    }
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - Hook Class
//
//static Class swizzle_hookClass(NSObject *self, NSError **error) {
//    NSCParameterAssert(self);
//    Class statedClass = self.class;
//    Class baseClass = object_getClass(self);
//    NSString *className = NSStringFromClass(baseClass);
//    
//    // Already subclassed
//    if ([className hasSuffix:QTSwizzlesSubclassSuffix]) {
//        return baseClass;
//        
//        // We swizzle a class object, not a single object.
//    }else if (class_isMetaClass(baseClass)) {
//        return swizzle_swizzleClassInPlace((Class)self);
//        // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
//    }else if (statedClass != baseClass) {
//        return swizzle_swizzleClassInPlace(baseClass);
//    }
//    
//    // Default case. Create dynamic subclass.
//    const char *subclassName = [className stringByAppendingString:QTSwizzlesSubclassSuffix].UTF8String;
//    Class subclass = objc_getClass(subclassName);
//    
//    if (subclass == nil) {
//        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
//        if (subclass == nil) {
//            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
//            QTSwizzleError(QTSwizzleErrorFailedToAllocateClassPair, errrorDesc);
//            return nil;
//        }
//        
//        swizzle_swizzleForwardInvocation(subclass);
//        swizzle_hookedGetClass(subclass, statedClass);
//        swizzle_hookedGetClass(object_getClass(subclass), statedClass);
//        objc_registerClassPair(subclass);
//    }
//    
//    object_setClass(self, subclass);
//    return subclass;
//}
//
//static NSString *const QTSwizzlesForwardInvocationSelectorName = @"__QTSwizzles_forwardInvocation:";
//static void swizzle_swizzleForwardInvocation(Class klass) {
//    NSCParameterAssert(klass);
//    // If there is no method, replace will act like class_addMethod.
//    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__swizzleS_ARE_BEING_CALLED__, "v@:@");
//    if (originalImplementation) {
//        class_addMethod(klass, NSSelectorFromString(QTSwizzlesForwardInvocationSelectorName), originalImplementation, "v@:@");
//    }
//    QTSwizzleLog(@"QTSwizzles: %@ is now QTSwizzle aware.", NSStringFromClass(klass));
//}
//
//static void swizzle_undoSwizzleForwardInvocation(Class klass) {
//    NSCParameterAssert(klass);
//    Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(QTSwizzlesForwardInvocationSelectorName));
//    Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
//    // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
//    IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
//    class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
//    
//    QTSwizzleLog(@"QTSwizzles: %@ has been restored.", NSStringFromClass(klass));
//}
//
//static void swizzle_hookedGetClass(Class class, Class statedClass) {
//    NSCParameterAssert(class);
//    NSCParameterAssert(statedClass);
//    Method method = class_getInstanceMethod(class, @selector(class));
//    IMP newIMP = imp_implementationWithBlock(^(id self) {
//        return statedClass;
//    });
//    class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - Swizzle Class In Place
//
//static void _swizzle_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
//    static NSMutableSet *swizzledClasses;
//    static dispatch_once_t pred;
//    dispatch_once(&pred, ^{
//        swizzledClasses = [NSMutableSet new];
//    });
//    @synchronized(swizzledClasses) {
//        block(swizzledClasses);
//    }
//}
//
//static Class swizzle_swizzleClassInPlace(Class klass) {
//    NSCParameterAssert(klass);
//    NSString *className = NSStringFromClass(klass);
//    
//    _swizzle_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
//        if (![swizzledClasses containsObject:className]) {
//            swizzle_swizzleForwardInvocation(klass);
//            [swizzledClasses addObject:className];
//        }
//    });
//    return klass;
//}
//
//static void swizzle_undoSwizzleClassInPlace(Class klass) {
//    NSCParameterAssert(klass);
//    NSString *className = NSStringFromClass(klass);
//    
//    _swizzle_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
//        if ([swizzledClasses containsObject:className]) {
//            swizzle_undoSwizzleForwardInvocation(klass);
//            [swizzledClasses removeObject:className];
//        }
//    });
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - QTSwizzle Invoke Point
//
//// This is a macro so we get a cleaner stack trace.
//#define swizzle_invoke(QTSwizzles, info) \
//for (QTSwizzleIdentifier *QTSwizzle in QTSwizzles) {\
//[QTSwizzle invokeWithInfo:info];\
//if (QTSwizzle.options & QTSwizzleOptionAutomaticRemoval) { \
//QTSwizzlesToRemove = [QTSwizzlesToRemove?:@[] arrayByAddingObject:QTSwizzle]; \
//} \
//}
//
//// This is the swizzled forwardInvocation: method.
//static void __swizzleS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
//    NSCParameterAssert(self);
//    NSCParameterAssert(invocation);
//    SEL originalSelector = invocation.selector;
//    SEL aliasSelector = swizzle_aliasForSelector(invocation.selector);
//    invocation.selector = aliasSelector;
//    QTSwizzlesContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
//    QTSwizzlesContainer *classContainer = swizzle_getContainerForClass(object_getClass(self), aliasSelector);
//    QTSwizzleInfo *info = [[QTSwizzleInfo alloc] initWithInstance:self invocation:invocation];
//    NSArray *QTSwizzlesToRemove = nil;
//    
//    // Before hooks.
//    swizzle_invoke(classContainer.beforeQTSwizzles, info);
//    swizzle_invoke(objectContainer.beforeQTSwizzles, info);
//    
//    // Instead hooks.
//    BOOL respondsToAlias = YES;
//    if (objectContainer.insteadQTSwizzles.count || classContainer.insteadQTSwizzles.count) {
//        swizzle_invoke(classContainer.insteadQTSwizzles, info);
//        swizzle_invoke(objectContainer.insteadQTSwizzles, info);
//    }else {
//        Class klass = object_getClass(invocation.target);
//        do {
//            if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
//                [invocation invoke];
//                break;
//            }
//        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
//    }
//    
//    // After hooks.
//    swizzle_invoke(classContainer.afterQTSwizzles, info);
//    swizzle_invoke(objectContainer.afterQTSwizzles, info);
//    
//    // If no hooks are installed, call original implementation (usually to throw an exception)
//    if (!respondsToAlias) {
//        invocation.selector = originalSelector;
//        SEL originalForwardInvocationSEL = NSSelectorFromString(QTSwizzlesForwardInvocationSelectorName);
//        if ([self respondsToSelector:originalForwardInvocationSEL]) {
//            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
//        }else {
//            [self doesNotRecognizeSelector:invocation.selector];
//        }
//    }
//    
//    // Remove any hooks that are queued for deregistration.
//    [QTSwizzlesToRemove makeObjectsPerformSelector:@selector(remove)];
//}
//#undef swizzle_invoke
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - QTSwizzle Container Management
//
//// Loads or creates the QTSwizzle container.
//static QTSwizzlesContainer *swizzle_getContainerForObject(NSObject *self, SEL selector) {
//    NSCParameterAssert(self);
//    SEL aliasSelector = swizzle_aliasForSelector(selector);
//    QTSwizzlesContainer *QTSwizzleContainer = objc_getAssociatedObject(self, aliasSelector);
//    if (!QTSwizzleContainer) {
//        QTSwizzleContainer = [QTSwizzlesContainer new];
//        objc_setAssociatedObject(self, aliasSelector, QTSwizzleContainer, OBJC_ASSOCIATION_RETAIN);
//    }
//    return QTSwizzleContainer;
//}
//
//static QTSwizzlesContainer *swizzle_getContainerForClass(Class klass, SEL selector) {
//    NSCParameterAssert(klass);
//    QTSwizzlesContainer *classContainer = nil;
//    do {
//        classContainer = objc_getAssociatedObject(klass, selector);
//        if (classContainer.hasQTSwizzles) break;
//    }while ((klass = class_getSuperclass(klass)));
//    
//    return classContainer;
//}
//
//static void swizzle_destroyContainerForObject(id<NSObject> self, SEL selector) {
//    NSCParameterAssert(self);
//    SEL aliasSelector = swizzle_aliasForSelector(selector);
//    objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
//}
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - Selector Blacklist Checking
//
//static NSMutableDictionary *swizzle_getSwizzledClassesDict() {
//    static NSMutableDictionary *swizzledClassesDict;
//    static dispatch_once_t pred;
//    dispatch_once(&pred, ^{
//        swizzledClassesDict = [NSMutableDictionary new];
//    });
//    return swizzledClassesDict;
//}
//
//static BOOL swizzle_isSelectorAllowedAndTrack(NSObject *self, SEL selector, QTSwizzleOptions options, NSError **error) {
//    static NSSet *disallowedSelectorList;
//    static dispatch_once_t pred;
//    dispatch_once(&pred, ^{
//        disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
//    });
//    
//    // Check against the blacklist.
//    NSString *selectorName = NSStringFromSelector(selector);
//    if ([disallowedSelectorList containsObject:selectorName]) {
//        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
//        QTSwizzleError(QTSwizzleErrorSelectorBlacklisted, errorDescription);
//        return NO;
//    }
//    
//    // Additional checks.
//    QTSwizzleOptions position = options&QTSwizzlePositionFilter;
//    if ([selectorName isEqualToString:@"dealloc"] && position != QTSwizzlePositionBefore) {
//        NSString *errorDesc = @"QTSwizzlePositionBefore is the only valid position when hooking dealloc.";
//        QTSwizzleError(QTSwizzleErrorSelectorDeallocPosition, errorDesc);
//        return NO;
//    }
//    
//    if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
//        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
//        QTSwizzleError(QTSwizzleErrorDoesNotRespondToSelector, errorDesc);
//        return NO;
//    }
//    
//    // Search for the current class and the class hierarchy IF we are modifying a class object
//    if (class_isMetaClass(object_getClass(self))) {
//        Class klass = [self class];
//        NSMutableDictionary *swizzledClassesDict = swizzle_getSwizzledClassesDict();
//        Class currentClass = [self class];
//        
//        QTSwizzleTracker *tracker = swizzledClassesDict[currentClass];
//        if ([tracker subclassHasHookedSelectorName:selectorName]) {
//            NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
//            NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
//            NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
//            QTSwizzleError(QTSwizzleErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
//            return NO;
//        }
//        
//        do {
//            tracker = swizzledClassesDict[currentClass];
//            if ([tracker.selectorNames containsObject:selectorName]) {
//                if (klass == currentClass) {
//                    // Already modified and topmost!
//                    return YES;
//                }
//                NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
//                QTSwizzleError(QTSwizzleErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
//                return NO;
//            }
//        } while ((currentClass = class_getSuperclass(currentClass)));
//        
//        // Add the selector as being modified.
//        currentClass = klass;
//        QTSwizzleTracker *subclassTracker = nil;
//        do {
//            tracker = swizzledClassesDict[currentClass];
//            if (!tracker) {
//                tracker = [[QTSwizzleTracker alloc] initWithTrackedClass:currentClass];
//                swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
//            }
//            if (subclassTracker) {
//                [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
//            } else {
//                [tracker.selectorNames addObject:selectorName];
//            }
//            
//            // All superclasses get marked as having a subclass that is modified.
//            subclassTracker = tracker;
//        }while ((currentClass = class_getSuperclass(currentClass)));
//    } else {
//        return YES;
//    }
//    
//    return YES;
//}
//
//static void swizzle_deregisterTrackedSelector(id self, SEL selector) {
//    if (!class_isMetaClass(object_getClass(self))) return;
//    
//    NSMutableDictionary *swizzledClassesDict = swizzle_getSwizzledClassesDict();
//    NSString *selectorName = NSStringFromSelector(selector);
//    Class currentClass = [self class];
//    QTSwizzleTracker *subclassTracker = nil;
//    do {
//        QTSwizzleTracker *tracker = swizzledClassesDict[currentClass];
//        if (subclassTracker) {
//            [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName];
//        } else {
//            [tracker.selectorNames removeObject:selectorName];
//        }
//        if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) {
//            [swizzledClassesDict removeObjectForKey:currentClass];
//        }
//        subclassTracker = tracker;
//    }while ((currentClass = class_getSuperclass(currentClass)));
//}
//
//@end
//
//@implementation QTSwizzleTracker
//
//- (id)initWithTrackedClass:(Class)trackedClass {
//    if (self = [super init]) {
//        _trackedClass = trackedClass;
//        _selectorNames = [NSMutableSet new];
//        _selectorNamesToSubclassTrackers = [NSMutableDictionary new];
//    }
//    return self;
//}
//
//- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
//    return self.selectorNamesToSubclassTrackers[selectorName] != nil;
//}
//
//- (void)addSubclassTracker:(QTSwizzleTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
//    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
//    if (!trackerSet) {
//        trackerSet = [NSMutableSet new];
//        self.selectorNamesToSubclassTrackers[selectorName] = trackerSet;
//    }
//    [trackerSet addObject:subclassTracker];
//}
//- (void)removeSubclassTracker:(QTSwizzleTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
//    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
//    [trackerSet removeObject:subclassTracker];
//    if (trackerSet.count == 0) {
//        [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName];
//    }
//}
//- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName {
//    NSMutableSet *hookingSubclassTrackers = [NSMutableSet new];
//    for (QTSwizzleTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) {
//        if ([tracker.selectorNames containsObject:selectorName]) {
//            [hookingSubclassTrackers addObject:tracker];
//        }
//        [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]];
//    }
//    return hookingSubclassTrackers;
//}
//- (NSString *)trackedClassName {
//    return NSStringFromClass(self.trackedClass);
//}
//
//- (NSString *)description {
//    return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];
//}
//
//@end
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - NSInvocation (QTSwizzles)
//
//@implementation NSInvocation (QTSwizzles)
//
//// Thanks to the ReactiveCocoa team for providing a generic solution for this.
//- (id)swizzle_argumentAtIndex:(NSUInteger)index {
//    const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
//    // Skip const type qualifier.
//    if (argType[0] == _C_CONST) argType++;
//    
//#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
//    if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
//        __autoreleasing id returnObj;
//        [self getArgument:&returnObj atIndex:(NSInteger)index];
//        return returnObj;
//    } else if (strcmp(argType, @encode(SEL)) == 0) {
//        SEL selector = 0;
//        [self getArgument:&selector atIndex:(NSInteger)index];
//        return NSStringFromSelector(selector);
//    } else if (strcmp(argType, @encode(Class)) == 0) {
//        __autoreleasing Class theClass = Nil;
//        [self getArgument:&theClass atIndex:(NSInteger)index];
//        return theClass;
//        // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
//    } else if (strcmp(argType, @encode(char)) == 0) {
//        WRAP_AND_RETURN(char);
//    } else if (strcmp(argType, @encode(int)) == 0) {
//        WRAP_AND_RETURN(int);
//    } else if (strcmp(argType, @encode(short)) == 0) {
//        WRAP_AND_RETURN(short);
//    } else if (strcmp(argType, @encode(long)) == 0) {
//        WRAP_AND_RETURN(long);
//    } else if (strcmp(argType, @encode(long long)) == 0) {
//        WRAP_AND_RETURN(long long);
//    } else if (strcmp(argType, @encode(unsigned char)) == 0) {
//        WRAP_AND_RETURN(unsigned char);
//    } else if (strcmp(argType, @encode(unsigned int)) == 0) {
//        WRAP_AND_RETURN(unsigned int);
//    } else if (strcmp(argType, @encode(unsigned short)) == 0) {
//        WRAP_AND_RETURN(unsigned short);
//    } else if (strcmp(argType, @encode(unsigned long)) == 0) {
//        WRAP_AND_RETURN(unsigned long);
//    } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
//        WRAP_AND_RETURN(unsigned long long);
//    } else if (strcmp(argType, @encode(float)) == 0) {
//        WRAP_AND_RETURN(float);
//    } else if (strcmp(argType, @encode(double)) == 0) {
//        WRAP_AND_RETURN(double);
//    } else if (strcmp(argType, @encode(BOOL)) == 0) {
//        WRAP_AND_RETURN(BOOL);
//    } else if (strcmp(argType, @encode(bool)) == 0) {
//        WRAP_AND_RETURN(BOOL);
//    } else if (strcmp(argType, @encode(char *)) == 0) {
//        WRAP_AND_RETURN(const char *);
//    } else if (strcmp(argType, @encode(void (^)(void))) == 0) {
//        __unsafe_unretained id block = nil;
//        [self getArgument:&block atIndex:(NSInteger)index];
//        return [block copy];
//    } else {
//        NSUInteger valueSize = 0;
//        NSGetSizeAndAlignment(argType, &valueSize, NULL);
//        
//        unsigned char valueBytes[valueSize];
//        [self getArgument:valueBytes atIndex:(NSInteger)index];
//        
//        return [NSValue valueWithBytes:valueBytes objCType:argType];
//    }
//    return nil;
//#undef WRAP_AND_RETURN
//}
//
//- (NSArray *)swizzles_arguments {
//    NSMutableArray *argumentsArray = [NSMutableArray array];
//    for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
//        [argumentsArray addObject:[self swizzle_argumentAtIndex:idx] ?: NSNull.null];
//    }
//    return [argumentsArray copy];
//}
//
//@end
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - QTSwizzleIdentifier
//
//@implementation QTSwizzleIdentifier
//
//+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(QTSwizzleOptions)options block:(id)block error:(NSError **)error {
//    NSCParameterAssert(block);
//    NSCParameterAssert(selector);
//    NSMethodSignature *blockSignature = swizzle_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
//    if (!swizzle_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
//        return nil;
//    }
//    
//    QTSwizzleIdentifier *identifier = nil;
//    if (blockSignature) {
//        identifier = [QTSwizzleIdentifier new];
//        identifier.selector = selector;
//        identifier.block = block;
//        identifier.blockSignature = blockSignature;
//        identifier.options = options;
//        identifier.object = object; // weak
//    }
//    return identifier;
//}
//
//- (BOOL)invokeWithInfo:(id<QTSwizzleInfo>)info {
//    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
//    NSInvocation *originalInvocation = info.originalInvocation;
//    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
//    
//    // Be extra paranoid. We already check that on hook registration.
//    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
//        QTSwizzleLogError(@"Block has too many arguments. Not calling %@", info);
//        return NO;
//    }
//    
//    // The `self` of the block will be the QTSwizzleInfo. Optional.
//    if (numberOfArguments > 1) {
//        [blockInvocation setArgument:&info atIndex:1];
//    }
//    
//    void *argBuf = NULL;
//    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
//        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
//        NSUInteger argSize;
//        NSGetSizeAndAlignment(type, &argSize, NULL);
//        
//        if (!(argBuf = reallocf(argBuf, argSize))) {
//            QTSwizzleLogError(@"Failed to allocate memory for block invocation.");
//            return NO;
//        }
//        
//        [originalInvocation getArgument:argBuf atIndex:idx];
//        [blockInvocation setArgument:argBuf atIndex:idx];
//    }
//    
//    [blockInvocation invokeWithTarget:self.block];
//    
//    if (argBuf != NULL) {
//        free(argBuf);
//    }
//    return YES;
//}
//
//- (NSString *)description {
//    return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];
//}
//
//- (BOOL)remove {
//    return swizzle_remove(self, NULL);
//}
//
//@end
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - QTSwizzlesContainer
//
//@implementation QTSwizzlesContainer
//
//- (BOOL)hasQTSwizzles {
//    return self.beforeQTSwizzles.count > 0 || self.insteadQTSwizzles.count > 0 || self.afterQTSwizzles.count > 0;
//}
//
//- (void)addQTSwizzle:(QTSwizzleIdentifier *)QTSwizzle withOptions:(QTSwizzleOptions)options {
//    NSParameterAssert(QTSwizzle);
//    NSUInteger position = options&QTSwizzlePositionFilter;
//    switch (position) {
//        case QTSwizzlePositionBefore:  self.beforeQTSwizzles  = [(self.beforeQTSwizzles ?:@[]) arrayByAddingObject:QTSwizzle]; break;
//        case QTSwizzlePositionInstead: self.insteadQTSwizzles = [(self.insteadQTSwizzles?:@[]) arrayByAddingObject:QTSwizzle]; break;
//        case QTSwizzlePositionAfter:   self.afterQTSwizzles   = [(self.afterQTSwizzles  ?:@[]) arrayByAddingObject:QTSwizzle]; break;
//    }
//}
//
//- (BOOL)removeQTSwizzle:(id)QTSwizzle {
//    for (NSString *QTSwizzleArrayName in @[NSStringFromSelector(@selector(beforeQTSwizzles)),
//                                        NSStringFromSelector(@selector(insteadQTSwizzles)),
//                                        NSStringFromSelector(@selector(afterQTSwizzles))]) {
//        NSArray *array = [self valueForKey:QTSwizzleArrayName];
//        NSUInteger index = [array indexOfObjectIdenticalTo:QTSwizzle];
//        if (array && index != NSNotFound) {
//            NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
//            [newArray removeObjectAtIndex:index];
//            [self setValue:newArray forKey:QTSwizzleArrayName];
//            return YES;
//        }
//    }
//    return NO;
//}
//
//- (NSString *)description {
//    return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeQTSwizzles, self.insteadQTSwizzles, self.afterQTSwizzles];
//}
//
//@end
//
/////////////////////////////////////////////////////////////////////////////////////////////
//#pragma mark - QTSwizzleInfo
//
//@implementation QTSwizzleInfo
//
//@synthesize arguments = _arguments;
//
//- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
//    NSCParameterAssert(instance);
//    NSCParameterAssert(invocation);
//    if (self = [super init]) {
//        _instance = instance;
//        _originalInvocation = invocation;
//    }
//    return self;
//}
//
//- (NSArray *)arguments {
//    // Lazily evaluate arguments, boxing is expensive.
//    if (!_arguments) {
//        _arguments = self.originalInvocation.swizzles_arguments;
//    }
//    return _arguments;
//}
//
//@end
